package educrypt.gui.ctr;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ExecutionException;

import javax.swing.JFileChooser;
import javax.swing.SwingWorker;

import educrypt.ciphers.Caesar;
import educrypt.ciphers.Vigenere;
import educrypt.ciphers.hill.Hill;
import educrypt.ciphers.playfair.Playfair;
import educrypt.commons.Alphabet;
import educrypt.commons.Global;
import educrypt.commons.Text;
import educrypt.database.EduCryptDatabase;
import educrypt.database.dao.AlphabetDAO;
import educrypt.database.dao.EquivalencesDAO;
import educrypt.database.dao.KeywordDAO;
import educrypt.exceptions.EduCryptException;
import educrypt.filter.Equivalences;
import educrypt.filter.Filter;
import educrypt.gui.view.CiphersOptionsView;
import educrypt.gui.view.CiphersResultView;
import educrypt.gui.view.EditorAlphabetView;
import educrypt.gui.view.EditorKeywordView;
import educrypt.gui.view.EducryptFileChooserView;
import educrypt.gui.view.MainView;
import educrypt.gui.view.TextFilterView;
import educrypt.gui.view.TextPreviewView;
import educrypt.gui.view.TextView;
import educrypt.io.Input;
import educrypt.io.Output;

public class CiphersCtr implements ActionListener, Observer {

	// View
	private MainView view;
	private TextView textView;
	private CiphersOptionsView optionsView;
	private CiphersResultView resultView;
	private TextPreviewView textPreviewView;
	private TextFilterView textFilterView;

	// Model
	private Text originalText, processedText;
	private List<Alphabet> alphabets;
	private List<String> keywords;

	public CiphersCtr(MainView view) {
		this.view = view;
		this.textView = view.getCiphersPanel().getTextPanel();
		this.optionsView = view.getCiphersPanel().getOptionsPanel();
		this.resultView = view.getCiphersPanel().getCiphersResultPanel();
		this.textPreviewView = view.getTextPreviewDialog();
		this.textFilterView = view.getCiphersPanel().getOptionsPanel()
				.getTextFilterDialog();

		initializeValues();
	}

	private void initializeValues() {
		this.optionsView.showCiphers(Global.CIPHERS_LIST);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		try {
			String command = e.getActionCommand();
			// File / Text
			if (command.equals(TextView.FILE_BROWSE)) {
				textView.showFileChooser();
			}

			if (command.equals(TextView.PREVIEW)) {
				textPreviewView.showPreview(originalText.getContent());
			}

			// Options
			if (command.equals(CiphersOptionsView.CIPHER_SELECTED)) {
				this.processCipherSelected();
			}
			if (command.equals(CiphersOptionsView.ALPHABET_SELECTED)) {
				this.processAlphabetSelected();
			}
			if (command.equals(CiphersOptionsView.KEYWORD_SELECTED)) {
				this.processKeywordSelected();
			}
			if (command.equals(CiphersOptionsView.ADD_ALPHABET)) {
				this.processAddAlphabet();
			}
			if (command.equals(CiphersOptionsView.ADD_KEYWORD)) {
				this.processAddKeyword();
			}
			if (command.equals(CiphersOptionsView.ENCRYPT)) {
				processEncryption();
			}
			if (command.equals(CiphersOptionsView.DECRYPT)) {
				processDecryption();
			}
			if (command.equals(CiphersOptionsView.FILTER_TEXT)) {
				// Getting equivalences from database
				Equivalences equivalences = EquivalencesDAO.getInstance()
						.findEquivalences();

				// Getting unknown symbols from text
				List<Character> unknownSymbols = Filter.getTextUnknownSymbols(
						this.originalText, this.alphabets.get(optionsView
								.getSelectedAlphabetIndex() - 1));

				// Adding unknown symbols to equivalences
				equivalences.addNewSymbols(unknownSymbols);

				// Getting positions in the equivalences of the unknown symbols
				List<Integer> symbolsPositoin = equivalences
						.getSymbolsPositions(unknownSymbols);

				// Showing equivalences
				textFilterView
						.showTableValues(equivalences.getAllSymbols(),
								equivalences.getAllEquivalentSymbols(),
								symbolsPositoin);

				textFilterView.open((Component) optionsView);
			}

			// Filter Dialog
			if (command.equals(TextFilterView.DO_FILTER)) {
				// Saving equivalences into the database
				List<Character> symbols = textFilterView.getSymbols();
				List<Character> equivalentSymbols = textFilterView
						.getEquivalences();
				Equivalences eq = new Equivalences(symbols, equivalentSymbols);
				EquivalencesDAO.getInstance().createEquivalences(eq);

				// Filtering text
				this.originalText = Filter.filterText(this.originalText,
						this.alphabets.get(optionsView
								.getSelectedAlphabetIndex() - 1), eq);
			}

			// Results
			if (command.equals(CiphersResultView.SAVE_RESULT)) {
				this.resultView.getSaveFileDialog().showSaveDialog();
			}

			// LoadView - SaveView
			if (command.equals(JFileChooser.APPROVE_SELECTION)) {
				JFileChooser fileChooser = (JFileChooser) e.getSource();
				int type = fileChooser.getDialogType();
				if (type == JFileChooser.OPEN_DIALOG) {
					this.processOpenFile();
				} else if (type == JFileChooser.SAVE_DIALOG) {
					this.processSaveResultToFile();
				}
			}
		} catch (EduCryptException ex) {
			view.showExceptionDialog(ex.getMessage());
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	private void processOpenFile() throws EduCryptException {
		EducryptFileChooserView fileChooser = textView.getFileChooser();
		File selectedFile = fileChooser.getSelectedFile();
		if (selectedFile.exists()) {
			textView.setFilePath(selectedFile.getAbsolutePath());
			textView.enablePreview(true);
			originalText = new Text();
			String textContent = Input.readTextFromFile(selectedFile
					.getAbsolutePath());
			originalText.setContent(textContent);
			optionsView.setPanelEnabled(true);
			resultView.setPanelEnabled(false);
		}
	}

	private void processSaveResultToFile() {
		EducryptFileChooserView saveFileView = resultView.getSaveFileDialog();
		File selectedFile = saveFileView.getSelectedFile();
		Output.writePlainText(this.processedText.getContent(),
				selectedFile.getAbsolutePath());
	}

	private void processCipherSelected() {
		int cipher = optionsView.getSelectedCipherIndex();
		if (cipher > 0) {
			showAlphabets(cipher);
		} else {
			optionsView.hideAlphabets();
		}
	}

	private void showAlphabets(int cipher) {
		this.alphabets = AlphabetDAO.getInstance().findByCipher(cipher);
		List<String> alphabetsNames = new ArrayList<String>();
		for (Alphabet alphabet : alphabets) {
			alphabetsNames.add(alphabet.getName());
		}
		optionsView.showAlphabets(alphabetsNames);
	}

	private void showAlphabetsIfEnabled(int cipher) {
		this.alphabets = AlphabetDAO.getInstance().findByCipher(cipher);
		List<String> alphabetsNames = new ArrayList<String>();
		for (Alphabet alphabet : alphabets) {
			alphabetsNames.add(alphabet.getName());
		}
		optionsView.showAlphabetsIfEnabled(alphabetsNames);
	}

	private void processKeywordSelected() {
		if (optionsView.getSelectedKeywordIndex() > 0) {
			optionsView.enableAction(true);
		} else {
			optionsView.enableAction(false);
		}
	}

	private void processAddKeyword() {
		int cipherType = optionsView.getSelectedCipherIndex();
		String alphabetName = optionsView.getSelectedAlphabetName();
		keywords = KeywordDAO.getInstance().find(cipherType, alphabetName);

		EditorKeywordView editorKeywordView = view.getEditorKeywordDialog();
		// Set the same selected cipher and alphabet
		editorKeywordView.setSelectedCipher(optionsView
				.getSelectedCipherIndex());
		editorKeywordView.setSelectedAlphabetName(alphabetName);
		// Open editor
		editorKeywordView.open((Component) optionsView);
	}

	private List<String> generateOffsets(int maxOffset) {
		List<String> offsets = new ArrayList<String>(maxOffset);
		for (int i = 1; i < maxOffset; i++) {
			offsets.add(String.valueOf(i));
		}
		return offsets;
	}

	private void processAlphabetSelected() {
		if (optionsView.getSelectedAlphabetIndex() > 0) {
			String alphabetName = optionsView.getSelectedAlphabetName();
			int cipherType = optionsView.getSelectedCipherIndex();
			if (cipherType == Global.CAESAR) {
				Alphabet alphabet = alphabets.get(optionsView
						.getSelectedAlphabetIndex() - 1);
				int maxOffset = alphabet.size();
				keywords = this.generateOffsets(maxOffset);
			} else {
				keywords = KeywordDAO.getInstance().find(cipherType,
						alphabetName);
			}
			optionsView.showKeywords(keywords);

			optionsView.enableFilter(true);
		} else {
			optionsView.hideKeywords();
			optionsView.enableAction(false);
			optionsView.enableFilter(false);
		}
	}

	private void processAddAlphabet() {
		int cipher = optionsView.getSelectedCipherIndex();
		EditorAlphabetView editorAlphabetView = view.getEditorAlphabetDialog();
		editorAlphabetView.setSelectedCipherGroup(cipher);
		editorAlphabetView.open((Component) optionsView);
	}

	private void processEncryption() throws EduCryptException,
			InterruptedException, ExecutionException {
		int cipher = optionsView.getSelectedCipherIndex();

		String selectedKeyword = keywords.get(optionsView
				.getSelectedKeywordIndex() - 1);
		Alphabet selectedAlphabet = alphabets.get(optionsView
				.getSelectedAlphabetIndex() - 1);

		EncryptWorker encryptWorker = new EncryptWorker(cipher,
				selectedAlphabet, selectedKeyword, originalText);
		encryptWorker.execute();
		view.showWaitDialog(true);
	}

	private void processDecryption() {
		int cipher = optionsView.getSelectedCipherIndex();
		String selectedKeyword = keywords.get(optionsView
				.getSelectedKeywordIndex() - 1);
		Alphabet selectedAlphabet = alphabets.get(optionsView
				.getSelectedAlphabetIndex() - 1);

		DecryptWorker decryptWorker = new DecryptWorker(cipher,
				selectedAlphabet, selectedKeyword, originalText);
		decryptWorker.execute();
		view.showWaitDialog(true);
	}

	private void processEncryptDecryptFinished(Text processedText) {
		this.view.showWaitDialog(false);
		this.processedText = processedText;

		this.resultView.setPanelEnabled(true);
		this.resultView.setResultText(processedText.getContent());
	}

	private class EncryptWorker extends SwingWorker<Text, Void> {
		private int cipherType;
		private Alphabet alphabet;
		private String keyword;
		private Text plainText, cipherText;

		public EncryptWorker(int cipherType, Alphabet alphabet, String keyword,
				Text plainText) {
			this.cipherType = cipherType;
			this.alphabet = alphabet;
			this.keyword = keyword;
			this.plainText = plainText;
		}

		@Override
		protected Text doInBackground() throws EduCryptException {
			if (cipherType == Global.CAESAR) {
				int selectedOffset = Integer.valueOf(keyword);
				System.out.println(selectedOffset);
				Caesar caesarCipher = new Caesar(alphabet, selectedOffset);
				cipherText = caesarCipher.encode(plainText);
			}
			if (cipherType == Global.VIGENERE) {
				Vigenere vigenereCipher = new Vigenere(alphabet, keyword);
				cipherText = vigenereCipher.encode(plainText);
			}
			if (cipherType == Global.PLAYFAIR) {
				Playfair playfairCipher = new Playfair(alphabet, keyword);
				cipherText = playfairCipher.encode(plainText);
			}
			if (cipherType == Global.HILL) {
				Hill hillCipher = new Hill(alphabet, keyword);
				cipherText = hillCipher.encode(plainText);
			}
			return cipherText;
		}

		@Override
		protected void done() {
			try {
				processEncryptDecryptFinished(get());
			} catch (Exception e) {
				view.showWaitDialog(false);
				view.showExceptionDialog(e.getMessage());
				e.printStackTrace();
			}
		}
	}

	private class DecryptWorker extends SwingWorker<Text, Void> {
		private int cipherType;
		private Alphabet alphabet;
		private String keyword;
		private Text cipherText, plainText;

		public DecryptWorker(int cipherType, Alphabet alphabet, String keyword,
				Text cipherText) {
			this.cipherType = cipherType;
			this.alphabet = alphabet;
			this.keyword = keyword;
			this.cipherText = cipherText;
		}

		@Override
		protected Text doInBackground() throws EduCryptException {
			if (cipherType == Global.CAESAR) {
				int selectedOffset = Integer.valueOf(keyword);
				Caesar caesarCipher = new Caesar(alphabet, selectedOffset);
				plainText = caesarCipher.decode(cipherText);
			}
			if (cipherType == Global.VIGENERE) {
				Vigenere vigenereCipher = new Vigenere(alphabet, keyword);
				plainText = vigenereCipher.decode(cipherText);
			}
			if (cipherType == Global.PLAYFAIR) {
				Playfair playfairCipher = new Playfair(alphabet, keyword);
				plainText = playfairCipher.decode(cipherText);
			}
			if (cipherType == Global.HILL) {
				Hill hillCipher = new Hill(alphabet, keyword);
				plainText = hillCipher.decode(cipherText);
			}
			return plainText;
		}

		@Override
		protected void done() {
			try {
				processEncryptDecryptFinished(get());
			} catch (Exception e) {
				view.showWaitDialog(false);
				view.showExceptionDialog(e.getMessage());
				e.printStackTrace();
			}
		}
	}

	@Override
	public void update(Observable o, Object arg) {
		if (o instanceof KeywordDAO) {

			Object[] details = (Object[]) arg;
			int modifiedKeywordCipher = (Integer) details[0];
			String modifiedKeywordAlphabet = (String) details[1];

			int cipher = optionsView.getSelectedCipherIndex();
			String alphabetName = optionsView.getSelectedAlphabetName();

			if (cipher == modifiedKeywordCipher
					&& alphabetName.equals(modifiedKeywordAlphabet)) {
				keywords = KeywordDAO.getInstance().find(cipher, alphabetName);
				optionsView.showKeywords(keywords);
			}
		}
		if (o instanceof AlphabetDAO) {
			int cipher = optionsView.getSelectedCipherIndex();
			showAlphabetsIfEnabled(cipher);
		}

		if (o instanceof EduCryptDatabase) {
			int cipher = optionsView.getSelectedCipherIndex();
			if (cipher > 0) {
				showAlphabetsIfEnabled(cipher);
			}
			if (optionsView.getSelectedAlphabetIndex() > 0) {
				String alphabetName = optionsView.getSelectedAlphabetName();
				this.keywords = KeywordDAO.getInstance().find(cipher,
						alphabetName);
			}
		}
	}
}
